#include "axbopenglwidget.h"

#define TIMEOUTMSEC 100

unsigned int VAO, VBO, EBO;
float ratio = 0.5;
float PI = 3.1415926;
QPoint deltaPos;
float fov = 45.0;

float vertices[] = {
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
     0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 1.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f, 1.0f, 0.0f,

     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
     0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
     0.5f, -0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f, 0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f,
     0.5f,  0.5f, -0.5f, 1.0f, 1.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
     0.5f,  0.5f,  0.5f, 1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f, 0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, 0.0f, 1.0f
};

QVector<QVector3D> cubePositions = {
    QVector3D( 0.0f,  0.0f,   0.0f),
    QVector3D( 2.0f,  5.0f, -15.0f),
    QVector3D(-1.5f, -2.2f,  -2.5f),
    QVector3D(-3.8f, -2.0f, -12.3f),
    QVector3D( 2.4f, -0.4f,  -3.5f),
    QVector3D(-1.7f,  3.0f,  -7.5f),
    QVector3D( 1.3f, -2.0f,  -2.5f),
    QVector3D( 1.5f,  2.0f,  -2.5f),
    QVector3D( 1.5f,  0.2f,  -1.5f),
    QVector3D(-1.3f,  1.0f,  -1.5f)
};

unsigned int indices[] ={
    0, 1, 3,    // first triangle
    1, 2, 3     // second triangle
};

AXBOpenGLWidget::AXBOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    setMouseTracking(true);
    setFocusPolicy(Qt::StrongFocus);
    connect(&timer, SIGNAL(timeout()), this, SLOT(on_timeout()));
    timer.start(100);
    m_time.start();

    // Camera position
    cameraPos = QVector3D(0.0f, 0.0f, 2.0f);
    // Camera direction
    cameraTarget = QVector3D(0.0f, 0.0f, 0.0f);
    cameraDirection = QVector3D(cameraPos - cameraTarget);
    cameraDirection.normalize();
    // Right axis
    up = QVector3D(0.0f, 1.0f, 0.0f);
    cameraRight = QVector3D::crossProduct(up, cameraDirection);
    // Up axis
    cameraUp = QVector3D::crossProduct(cameraDirection, cameraRight);
    cameraFront = QVector3D(0.0, 0.0, -1.0);
}

AXBOpenGLWidget::~AXBOpenGLWidget()
{
    makeCurrent();
    glDeleteBuffers(1, &EBO);
    glDeleteBuffers(1, &VBO);
    glDeleteVertexArrays(1, &VAO);
    doneCurrent();
}

void AXBOpenGLWidget::drawShape(AXBOpenGLWidget::Shape shape)
{
    m_shape = shape;
    update();
}

void AXBOpenGLWidget::setWireframe(bool wireframe)
{
    makeCurrent();
    if (wireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    else
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    update();
    doneCurrent();
}

void AXBOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    // 创建渲染管线
    shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/shapes.vert");
    shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/shapes.frag");
    bool success = shaderProgram.link();
    if (!success)
        qDebug() << "ERR:" << shaderProgram.log();

    // 创建VAO、VBO和EBO对象,并赋予ID
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    glGenBuffers(1, &EBO);

    // 绑定当前VAO、VBO和EBO
    glBindVertexArray(VAO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);

    // 设置缓冲区
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // 告知显卡如何解析缓冲区里的属性值,索引为0
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    // 开启VAO管理的第0个属性值
    glEnableVertexAttribArray(0);

    // 告知显卡如何解析缓冲区里的属性值,索引为1
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // 开启VAO管理的第1个属性值
    glEnableVertexAttribArray(1);

    // 设置复用顶点索引
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    textureWall = new QOpenGLTexture(QImage(":/Images/wall.png").mirrored());
    textureSmile = new QOpenGLTexture(QImage(":/Images/awesomeface.png").mirrored());
    textureSmall = new QOpenGLTexture(QImage(":/Images/small.png").mirrored());

    shaderProgram.bind();
    shaderProgram.setUniformValue("textureWall", 0);
    shaderProgram.setUniformValue("textureSmile", 1);
    shaderProgram.setUniformValue("textureSmall", 2);
    shaderProgram.setUniformValue("ratio", ratio);

    textureSmall->bind(2);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 解除绑定VAO、VBO和EBO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}

void AXBOpenGLWidget::resizeGL(int w, int h)
{
    Q_UNUSED(w);
    Q_UNUSED(h);
}

void AXBOpenGLWidget::paintGL()
{
//    unsigned int time = QTime::currentTime().msec();

    QMatrix4x4 model;
    QMatrix4x4 view;
    QMatrix4x4 projection;
    projection.perspective(fov, (float)width() / height(), 0.1, 100);

    float time = m_time.elapsed() / 1000.0;
    view.lookAt(cameraPos, cameraFront+cameraPos, up);

    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shaderProgram.bind();
    glBindVertexArray(VAO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    shaderProgram.setUniformValue("projection", projection);

    switch (m_shape) {
    case Rect:
        textureWall->bind(0);
        textureSmile->bind(1);
        textureSmall->bind(2);
        shaderProgram.setUniformValue("view", view);
        foreach (auto item, cubePositions) {
            model.setToIdentity();
            model.translate(item);
            model.rotate(time, 1.0f, 5.0f, 0.5f);
            shaderProgram.setUniformValue("model", model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }
        break;
    default:
        break;
    }

}

void AXBOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    float cameraSpeed = 2.5 * TIMEOUTMSEC / 1000.0;
    switch (event->key()) {
    case Qt::Key_Up: ratio += 0.1; break;
    case Qt::Key_Down: ratio -= 0.1; break;
    case Qt::Key_W: cameraPos += cameraSpeed * cameraFront; break;
    case Qt::Key_S: cameraPos -= cameraSpeed * cameraFront; break;
    case Qt::Key_D: cameraPos += cameraSpeed * cameraRight; break;
    case Qt::Key_A: cameraPos -= cameraSpeed * cameraRight; break;
    default: break;
    }
    if (ratio < 0) ratio = 0;
    if (ratio > 1) ratio = 1;

    makeCurrent();
    shaderProgram.bind();
    shaderProgram.setUniformValue("ratio", ratio);
    doneCurrent();
    update();
}

void AXBOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    static float yaw = -90;
    static float pitch = 0;
    static QPoint lastPos(width() / 2, height() / 2);
    auto currentPos = event->pos();
    deltaPos = currentPos - lastPos;
    lastPos = currentPos;
    float sensitivity = 0.1f;
    deltaPos *= sensitivity;
    yaw += deltaPos.x();
    pitch -= deltaPos.y();
    if (pitch > 89.0f) pitch = 89.0f;
    if (pitch < -89.0f) pitch = -89.0f;
    cameraFront.setX(cos(yaw * PI / 180) * cos(pitch * PI /180));
    cameraFront.setY(sin(pitch * PI / 180));
    cameraFront.setZ(sin(yaw * PI / 180) * cos(pitch * PI / 180));
    cameraFront.normalize();
    update();
}

void AXBOpenGLWidget::wheelEvent(QWheelEvent *event)
{
    if (fov >= 1.0f && fov <= 75.0f)
        fov -= event->angleDelta().y() / 120;
    if (fov <= 1.0f) fov = 1.0f;
    if (fov >= 75.0f) fov = 75.0f;

}

void AXBOpenGLWidget::on_timeout()
{
    update();
}

